package com.cloud.utils;

import com.alibaba.fastjson.JSONArray;

import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

/**
 * 判断坐标点位置算法
 *
 * @author yzj
 */
public class GisPointUtils {

    private static double EARTH_RADIUS = 6378.137;

    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }

    /**
     * 通过经纬度获取距离(单位:米)
     */
    public static double getDistance(double lng1, double lat1, double lng2, double lat2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lng1) - rad(lng2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
                + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000d) / 10000d;
        return s * 1000;
    }

    /**
     * 计算两点之间的距离(单位:米)
     */
    public static Double getDistance(String lng1, String lat1, String lng2, String lat2) {
        double radLng1 = Double.parseDouble(lng1);
        double radLat1 = Double.parseDouble(lat1);
        double radLng2 = Double.parseDouble(lng2);
        double radLat2 = Double.parseDouble(lat2);
        return getDistance(radLng1, radLat1, radLng2, radLat2);
    }

    /**
     * 判断一个点是否在圆形区域内
     *
     * @param radius 半径(单位米)
     * @param lng1   圆心经度
     * @param lat1   圆心纬度
     * @param lng2   坐标经度
     * @param lat2   坐标纬度
     */
    public static boolean isInCircle(double lng1, double lat1, double lng2, double lat2, String radius) {
        return getDistance(lng1, lat1, lng2, lat2) < Double.parseDouble(radius);
    }

    /**
     * 判断一个点是否在圆形区域内
     *
     * @param radius 半径(单位米)
     * @param lng1   圆心经度
     * @param lat1   圆心纬度
     * @param lng2   坐标经度
     * @param lat2   坐标纬度
     */
    public static boolean isInCircle(String lng1, String lat1, String lng2, String lat2, String radius) {
        double radLng1 = Double.parseDouble(lng1);
        double radLat1 = Double.parseDouble(lat1);
        double radLng2 = Double.parseDouble(lng2);
        double radLat2 = Double.parseDouble(lat2);
        return isInCircle(radLng1, radLat1, radLng2, radLat2, radius);
    }

    /**
     * 一个点是否在多边形内
     *
     * @param point   要判断的点的横纵坐标
     * @param polygon 组成的顶点坐标集合
     * @return
     */
    public static boolean isPolygonCheck(Point2D.Double point, List<Point2D.Double> polygon) {
        GeneralPath peneralPath = new GeneralPath();
        Point2D.Double first = polygon.get(0);
        // 通过移动到指定坐标（以双精度指定）将一个点添加到路径中
        peneralPath.moveTo(first.x, first.y);
        for (int i = 1; i < polygon.size(); i++) {
            // 通过绘制一条从当前坐标到新指定坐标（以双精度指定）的直线,将一个点添加到路径中。
            peneralPath.lineTo(polygon.get(i).x, polygon.get(i).y);
        }
        // 将几何多边形封闭
        peneralPath.lineTo(first.x, first.y);
        peneralPath.closePath();
        // 判断指定的Point2D是否在Shape的边界内
        return peneralPath.contains(point);
    }

    /**
     * 一个点是否在多边形内
     *
     * @param lng     要判断的点的横坐标
     * @param lat     要判断的点的纵坐标
     * @param polygon 组成的顶点坐标集合
     * @return
     */
    public static boolean isPolygonCheck(String lng, String lat, String polygon) {
        Point2D.Double point = new Point2D.Double();
        point.setLocation(Double.parseDouble(lng), Double.parseDouble(lat));
        List<Point2D.Double> point2DList = pointStrToPoint2D(polygon);
        if (point2DList.size() > 0) {
            return isPolygonCheck(point, pointStrToPoint2D(polygon));
        } else {
            return false;
        }
    }

    /**
     * 判断是否偏离航线在允许范围内
     *
     * @param point  -- 实时点用于判断此点是否偏离航线
     * @param points -- 航线组成的点坐标
     * @param range  -- 允许偏离航线的距离 单位：m
     * @return true   -- 未偏离航线 ; false -- 偏离航线
     */
    public static boolean pointToPintLine(Point2D.Double point, List<Point2D.Double> points, double range) {
        double minDistance = -1;
        for (int i = 0; i < points.size() - 1; i++) {
            // 获取线段的x取值范围和Y的取值范围
            double[] rangeX = new double[2];
            double[] rangeY = new double[2];
            if (points.get(i).x > points.get(i + 1).x) {
                rangeX[0] = points.get(i + 1).x;
                rangeX[1] = points.get(i).x;
            } else {
                rangeX[0] = points.get(i).x;
                rangeX[1] = points.get(i + 1).x;
            }
            if (points.get(i).y > points.get(i + 1).y) {
                rangeY[0] = points.get(i + 1).y;
                rangeY[1] = points.get(i).y;
            } else {
                rangeY[0] = points.get(i).y;
                rangeY[1] = points.get(i + 1).y;
            }
            // 根据两点求出直线方程AX+BY+C=0中,A B C 的值
            double a = points.get(i + 1).y - points.get(i).y;
            double b = points.get(i).x - points.get(i + 1).x;
            double c = points.get(i + 1).x * points.get(i).y - points.get(i).x * points.get(i + 1).y;
            // 求点到直线的垂足以及距离,得到垂足点
            Point2D.Double foot = getFootOfPerpendicular(point.x, point.y, a, b, c);
            // 得到距离
            double distance = getDistance(point.x, point.y, foot.x, foot.y);
            // 判断垂足是否在线段上
            if (foot.x >= rangeX[0] && foot.x <= rangeX[1] && foot.y >= rangeY[0] && foot.y <= rangeY[1]) {
                /**
                 * 1.如果在线段上则记录值
                 * 2.跟minDistance比较,如果小于目前值则进行替换(若是初始值(-1)也进行替换)
                 */
                if ((minDistance == -1) || (minDistance != -1 && distance < minDistance)) {
                    minDistance = distance;
                }
            }
        }
        /**
         * 1.看是否minDistance是否是初始值,
         * 2.如果是初始值则再次计算点到首末两点的距离,若均大于allowRange则认为偏离航线
         * 3.如果不是初始值则判断最小值是否小于allowRange
         */
        if (minDistance == -1) {
            // 计算点到首末两点的距离
            Point2D.Double startPoint = points.get(0);
            Point2D.Double endPoint = points.get(points.size() - 1);
            double startPointDistance = getDistance(point.x, point.y, startPoint.x, startPoint.y);
            double endPointDistance = getDistance(point.x, point.y, endPoint.x, endPoint.y);
            double distance = (startPointDistance <= endPointDistance ? startPointDistance : endPointDistance);
            minDistance = distance;
        }
        if (minDistance <= range) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 判断是否偏离航线在允许范围内
     *
     * @param lng,lat -- 实时点用于判断此点是否偏离航线
     * @param polygon -- 航线组成的点坐标
     * @param range   -- 允许偏离航线的距离 单位：m
     * @return true   -- 未偏离航线 ; false -- 偏离航线
     */
    public static boolean pointToPintLine(String lng, String lat, String polygon, double range) {
        Point2D.Double point = new Point2D.Double();
        point.setLocation(Double.parseDouble(lng), Double.parseDouble(lat));
        List<Point2D.Double> point2DList = pointStrToPoint2D(polygon);
        if (point2DList.size() > 0) {
            return pointToPintLine(point, point2DList, range);
        } else {
            return false;
        }
    }

    /**
     * Description 求点到直线的垂足
     *
     * @param x1 点横坐标
     * @param y1 点纵坐标
     * @param A  直线方程一般式系数A
     * @param B  直线方程一般式系数B
     * @param C  直线方程一般式系数C
     * @return 垂足点
     */
    private static Point2D.Double getFootOfPerpendicular(double x1, double y1, double A, double B, double C) {
        if (A * A + B * B < 1e-13) {
            return null;
        }
        if (Math.abs(A * x1 + B * y1 + C) < 1e-13) {
            return new Point2D.Double(x1, y1);
        } else {
            double newX = (B * B * x1 - A * B * y1 - A * C) / (A * A + B * B);
            double newY = (-A * B * x1 + A * A * y1 - B * C) / (A * A + B * B);
            return new Point2D.Double(newX, newY);
        }
    }

    /**
     * Description 将点坐标字符串转换为点坐标集合
     *
     * @param points 点坐标字符串(格式为二位数组)
     * @return 点坐标集合
     */
    private static List<Point2D.Double> pointStrToPoint2D(String points) {
        List<Point2D.Double> polygon = new ArrayList<>();
        try {
            List<String> pointList = JSONArray.parseArray(points, String.class);
            for (String point : pointList) {
                Point2D.Double point2D = new Point2D.Double();
                List<Double> pointVo = JSONArray.parseArray(point, Double.class);
                point2D.setLocation(pointVo.get(0), pointVo.get(1));
                polygon.add(point2D);
            }
        } catch (Exception e) {
            return polygon;
        }
        return polygon;
    }

}
